<?php
/**
 * @category ContentEngine
 * @package Goatherd\Content\Engine
 *
 * @author Copyright (c) 2012 Maik Penz <maik@phpkuh.de>
 * @version $Id: Source.php 91 2012-03-29 22:11:54Z maik@phpkuh.de $
 *
 * This file is part of Goatherd library.
 *
 * Goatherd library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Goatherd library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Goatherd library. If not, see <http://www.gnu.org/licenses/>.
 */
namespace Goatherd\Content\Engine\Source;

use Goatherd\Content\Engine\Writer\IWriter;

use Goatherd\Content\Engine\Reader\IReader;

use Goatherd\Commons\Observer\SubjectTrait;

use Goatherd\Magic\DuckableTrait;

/**
 *
 * @todo is it necessary to provide a direct reader/ writer access?
 * @todo implement config setters
 *
 * @category ContentEngine
 * @package Goatherd\Content\Engine
 * @subpackage Source
 */
class Source
implements ISource
{
    use SubjectTrait;
    use DuckableTrait { attachDuck as protected; detachDuck as protected; getDuck as protected; }

    /**
     *
     * @var string
     */
    const KEY_WRITER = 'writer';

    /**
     *
     * @var string
     */
    const KEY_READER = 'reader';

    /**
     *
     * @var string
     */
    const PRE_EVENT = 'pre';

    /**
     *
     * @var string
     */
    const POST_EVENT = 'post';

    /**
     * Default item class name
     *
     * @var string
     */
    protected $_itemClass = '\\Goatherd\\Content\\Engine\\Queue\\Item';

    /**
     *
     * @param array|Traversable $options
     */
    public function __construct($options = array())
    {
        // TODO apply options
        /*
         * Options should include:
         * - reader
         * - writer
         * - item class name
         * - mappers
         *
         * Any traversable object should be fine as input.
         */
        $this->init();
    }

    /**
     *
     */
    public function init() {}

    /**
     * Reader is enabled for ducktyping and can not be accessed directly.
     *
     * (non-PHPdoc)
     * @see Goatherd\Content\Engine\Source.ISource::setReader()
     */
    public function setReader(IReader $reader)
    {
        $this->attachDuck(self::KEY_READER, $reader);

        return $this;
    }

    /**
     * Writer is enabled for ducktyping and can not be accessed directly.
     *
     * (non-PHPdoc)
     * @see Goatherd\Content\Engine\Source.ISource::setWriter()
     */
    public function setWriter(IWriter $writer)
    {
        $this->attachDuck(self::KEY_WRITER, $writer);

        return $this;
    }

    /**
     * Shortcode for writerCreateItem() method.
     * Uses default item class and pre-sets source dependency.
     *
     * (non-PHPdoc)
     * @see Goatherd\Content\Engine\Source.ISource::createItem()
     */
    public function createItem()
    {
        $item = $this->writerCreateItem($this->_itemClass, $this);

        return $item;
    }

    /**
     * (non-PHPdoc)
     * @see Goatherd\Content\Engine\Source.ISource::getQueue()
     */
    public function getQueue()
    {
        return $this->_queue;
    }

    /**
     * Pre __call event.
     *
     * @param string $method
     * @param array $args
     */
    protected function _firePreEvent($method, array $args)
    {
        list($duckName, $methodName)
            = preg_split('/(?!^)(?=[[:upper:]])/u', $method, 2);

        // prepare event
        $event = [
            'duck' => $duckName,
            'method' => $methodName,
            'arguments' => $args,
            'result' => null,
        ];
        $this->setEventData($event);

        // fire event
        $this->setEventName(self::PRE_EVENT . ucfirst($method));
        $this->notify();
    }

    /**
     * Post __call event.
     *
     * @param mixed $result
     */
    protected function _firePostEvent($result)
    {
        // prepare event
        $event = $this->getEventData();
        $event['result'] = $result;
        $this->setEventData($event);

        // fire event
        $this->setEventName(self::POST_EVENT . ucfirst($method));
        $this->notify();
    }

    /**
     * Extend duck-typing with pre/ post events.
     * Invocation is disabled.
     *
     * @param string $method
     * @param array $args
     * @throws \InvalidArgumentException
     * @return mixec
     */
    public function __call($method, $args)
    {
        if (!is_string($method) || !ctype_alpha($method) || ctype_lower($method)) {
            throw new \InvalidArgumentException('Method must be camel cased ' .
                    'letters only. Duck invocation is not supported.');
        }

        // handle pre-event
        $this->_firePreEvent($method, $args);

        // handle manipulation
        $event = $this->getEventData();
        $args = $event['args'];
        $result = $event['result'];

        // method call (if not canceled)
        $result === null and $result = parent::__call($method, $args);

        $this->_firePostEvent($result);

        return $result;
    }

    /**
     * (non-PHPdoc)
     * @see Goatherd\Content\Engine\Source.ISource::register()
     */
    public function register() {
    }

    /**
     * (non-PHPdoc)
     * @see Goatherd\Content\Engine\Source.ISource::unregister()
     */
    public function unregister() {
    }
}